筆記目錄

Skip to content

淺談 Flag Enum 的應用與心得

TLDR

  • Flag Enum 透過 [Flags] 屬性與位元運算,能有效管理多重狀態組合。
  • 定義時應使用 2 的 N 次方(如 1 << 0, 1 << 1)確保旗標獨立。
  • 建議使用 HasFlag 方法進行狀態判斷,程式碼更具可讀性。
  • 應避免使用 NOT (~) 運算符定義複合列舉項目,以免產生非預期的數值結果。
  • 建議 Flag Enum 型別名稱使用複數(如 Permissions),一般 Enum 使用單數(如 DayOfWeek)。
  • 若 Enum 可能會擴充項目,應謹慎定義並使用 All 類型的組合值。

Flag Enum 的定義與設計規範

什麼情況下會遇到這個問題:當需要定義一組支援多重狀態組合的列舉,且希望程式碼具備高可讀性與擴充性時。

Flag Enum 透過 [Flags] 屬性標記,並利用位元運算來組合多個狀態。定義時,每個旗標必須是 2 的 N 次方,以確保每個位元都是獨立的。

csharp
[Flags]
enum Permissions {
    None = 0,
    CanQuery = 1 << 0,  // 1
    CanCreate = 1 << 1, // 2
    CanUpdate = 1 << 2, // 4
    CanDelete = 1 << 3  // 8
}

命名與維護建議

  • 命名規範:Microsoft 建議 Flag Enum 使用複數命名(如 Permissions),一般 Enum 使用單數命名(如 DayOfWeek)。
  • 擴充性考量:若定義了 All 組合值,當未來新增列舉項目時,必須同步更新 All 的定義,否則會導致邏輯錯誤。
  • 避免使用 NOT (~):使用 ~ 定義複合值容易導致 ToString() 輸出為數值而非名稱,且在判斷包含關係時容易產生邏輯偏差,應盡量避免。

Flag Enum 的操作與判斷

什麼情況下會遇到這個問題:當需要對多重狀態進行聯集、交集、補集運算,或檢查某個狀態是否存在於組合中時。

位元運算應用

  • OR (|):用於合併狀態(聯集)。
  • AND (&):用於篩選狀態(交集)。
  • XOR (^):用於反轉狀態(對稱差集)。
  • NOT (~):用於排除狀態(補集)。

若要從組合中移除特定項目,建議使用以下方式:

csharp
// 移除 CanCreate
Permissions result = Permissions.CanUpsert & ~Permissions.CanCreate;

狀態判斷

判斷是否包含特定旗標,建議優先使用 .HasFlag() 方法,其語意比手動進行位元運算更清晰。

csharp
// 使用 HasFlag 判斷
bool hasCreate = Permissions.CanUpsert.HasFlag(Permissions.CanCreate);

// 傳統位元運算判斷
bool hasCreate = (Permissions.CanUpsert & Permissions.CanCreate) == Permissions.CanCreate;

關於 None 的判斷

即使定義了 None = 0,使用 HasFlag(Permissions.None) 永遠會回傳 true,因為空集合在邏輯上被視為任何集合的子集。

異動歷程

  • 2023-12-05 初版文件建立。